import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from langchain.agents import tool
from langchain.agents import initialize_agent, Tool
from langchain.agents import AgentType
from langchain_community.llms.tongyi import Tongyi
from django.http import JsonResponse
import os
import json
import time
from rest_framework.views import APIView

# 初始化LLM
llm = Tongyi()

# 数据文件路径
DATA_FILE = "user_goods_data.csv"
SIMILARITY_CACHE = "user_similarity.json"


# 工具1：数据收集，写入CSV文件
@tool
def collect_goods(input: str) -> str:
    """
    当用户浏览或购买了商品后，记录用户行为数据
    输入格式：用户id,商品id,数量,行为类型(浏览/购买)
    例如：1001,2003,1,购买
    """
    try:
        user_id, goods_id, count, action = input.split(',')

        # 检查文件是否存在，不存在则创建并添加表头
        if not os.path.exists(DATA_FILE):
            df = pd.DataFrame(columns=['userid', 'goodsid', 'count', 'action'])
            df.to_csv(DATA_FILE, index=False)

        # 追加数据
        new_data = pd.DataFrame({
            'userid': [user_id],
            'goodsid': [goods_id],
            'count': [count],
            'action': [action]
        })

        new_data.to_csv(DATA_FILE, mode='a', header=False, index=False)

        return f"成功记录用户{user_id}对商品{goods_id}的{action}行为，数量{count}"
    except Exception as e:
        return f"数据收集失败: {str(e)}。请使用格式：用户id,商品id,数量,行为类型"


# 工具2：计算用户相似度，取前10个用户
@tool
def similarity(input: str) -> str:
    """
    分析用户相似度，获取最相似的前10个用户
    输入格式：目标用户id
    例如：1001
    """
    try:
        user_id = input.strip()

        # 检查数据文件是否存在
        if not os.path.exists(DATA_FILE):
            return "暂无用户行为数据，无法计算相似度"

        # 加载数据
        df = pd.read_csv(DATA_FILE)

        # 数据预处理
        # 计算用户-商品矩阵（购买行为权重6，浏览行为权重4）
        df['weight'] = df['action'].map({'购买': 6, '浏览': 4}).fillna(1)
        user_item_matrix = df.pivot_table(
            index='userid',
            columns='goodsid',
            values='weight',
            aggfunc='sum',
            fill_value=0
        )

        # 计算余弦相似度
        similarity_matrix = cosine_similarity(user_item_matrix)

        np.fill_diagonal(similarity_matrix, 0)  # 自己与自己的相似度设为0

        # 获取目标用户索引

        if int(user_id) not in user_item_matrix.index:
            return f"用户{user_id}不存在于数据中"

        user_idx = user_item_matrix.index.get_loc(int(user_id))

        # 获取最相似的10个用户
        # 提取目标用户与所有其他用户的相似度向量（一维数组）
        similar_indices = np.argsort(similarity_matrix[user_idx])[::-1][:10]
        # 最相似用户的 ID 列表（通过索引从user_item_matrix.index中获取）
        similar_users = user_item_matrix.index[similar_indices].tolist()
        # 对应的相似度分数列表
        similarity_scores = similarity_matrix[user_idx][similar_indices].tolist()

        # 缓存相似度结果
        similarity_data = {
            user_id: {
                'similar_users': similar_users,
                'scores': similarity_scores,
                'timestamp': int(time.time())
            }
        }

        with open(SIMILARITY_CACHE, 'w') as f:
            json.dump(similarity_data, f)

        result = [f"用户{similar_users[i]}: 相似度{similarity_scores[i]:.4f}" for i in range(len(similar_users))]
        return f"与用户{user_id}最相似的10个用户：\n" + "\n".join(result)

    except Exception as e:
        print(e)
        return f"相似度计算失败: {str(e)}"


@tool
def recommend_product(input: str) -> str:
    """
    基于用户相似度为用户推荐商品
    输入格式：目标用户id,推荐数量(可选，默认为5)
    例如：1001,8
    """
    try:
        # 分割输入参数，获取目标用户ID和推荐数量
        params = input.split(',')
        user_id = params[0].strip()  # 提取并去除用户ID前后空格
        # 若输入中包含推荐数量则使用该值，否则默认推荐5个商品
        num_recommendations = int(params[1].strip()) if len(params) > 1 else 5

        # 检查相似度缓存文件是否存在，不存在则无法推荐
        if not os.path.exists(SIMILARITY_CACHE):
            return "请先计算用户相似度"

        # 读取相似度缓存文件，获取用户相似度数据
        with open(SIMILARITY_CACHE, 'r') as f:
            similarity_data = json.load(f)

        # 检查目标用户是否存在于相似度数据中
        if user_id not in similarity_data:
            return f"没有用户{user_id}的相似度数据，请先计算"

        # 从相似度数据中获取与目标用户最相似的用户列表
        similar_users = similarity_data[user_id]['similar_users']

        # 检查用户行为数据文件是否存在，不存在则无法生成推荐
        if not os.path.exists(DATA_FILE):
            return "暂无用户行为数据，无法生成推荐"

        # 加载用户行为数据到DataFrame
        df = pd.read_csv(DATA_FILE)

        # 获取目标用户已交互（购买/浏览）的商品ID集合
        # 将userid转换为整数后筛选目标用户数据，再提取goodsid列转为列表并去重
        user_items = set(df[df['userid'] == int(user_id)]['goodsid'].tolist())

        # 初始化推荐商品字典（键为商品ID，值为推荐分数）
        recommendations = {}

        # 遍历每个相似用户，提取其交互的商品
        for similar_user in similar_users:
            # 筛选当前相似用户的所有行为数据
            similar_user_items = df[df['userid'] == int(similar_user)]

            # 遍历相似用户的每个行为记录
            for _, item in similar_user_items.iterrows():
                # 若商品未被目标用户交互过，则计算推荐分数
                if item['goodsid'] not in user_items:
                    # 根据相似用户列表索引，获取对应的相似度分数
                    similarity_score = next(
                        score for idx, score in enumerate(similarity_data[user_id]['scores'])
                        if similar_users[idx] == similar_user
                    )

                    # 根据行为类型设置权重（购买行为权重5，其他行为权重1）
                    action_weight = 5 if item['action'] == '购买' else 1
                    # 计算推荐分数：相似度 * 行为权重 * 购买数量
                    recommendation_score = similarity_score * action_weight * item['count']

                    # 累加同一商品的推荐分数（若已存在则累加，否则新建）
                    if item['goodsid'] in recommendations:
                        recommendations[item['goodsid']] += recommendation_score
                    else:
                        recommendations[item['goodsid']] = recommendation_score

        # 按推荐分数从高到低排序推荐商品
        sorted_recommendations = sorted(recommendations.items(), key=lambda x: x[1], reverse=True)

        # 模拟商品信息映射（实际应用中应从数据库或服务获取）
        product_info = {
            'g001': "夏季连衣裙",
            'g002': "男士T恤",
            'g003': "运动鞋"
        }

        # 生成推荐结果列表
        result = []
        # 取前num_recommendations个商品，格式化推荐信息
        for goods_id, score in sorted_recommendations[:num_recommendations]:
            # 从商品信息映射中获取商品名称，若不存在则使用默认格式
            product_name = product_info.get(goods_id, f"商品{goods_id}")
            result.append(f"{product_name} (商品ID: {goods_id}, 推荐分数: {score:.2f})")
        print("result", result)
        # 返回格式化的推荐结果字符串
        return f"为用户{user_id}推荐的商品：\n" + "\n".join(result)

    except Exception as e:
        # 捕获所有异常并返回错误信息
        return f"商品推荐失败: {str(e)}"


# API视图
class Recommend(APIView):
    def get(self, request):
        tools = [
            Tool(
                name="collect_goods",
                func=collect_goods,
                description="当用户浏览或购买了商品后，记录用户行为数据。输入格式：用户id,商品id,数量,行为类型(浏览/购买)，例如：1001,2003,1,购买"
            ),
            Tool(
                name="similarity",
                func=similarity,
                description="分析用户相似度，获取最相似的前10个用户。输入格式：目标用户id，例如：1001"
            ),
            Tool(
                name="recommend_product",
                func=recommend_product,
                description="基于用户相似度为用户推荐商品。输入格式：目标用户id,推荐数量(可选，默认为5)，例如：1001,8"
            )
        ]

        agent = initialize_agent(
            tools,
            llm,
            agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
            verbose=True
        )

        try:
            # 获取用户输入数据
            action = request.GET.get('action')
            userid = request.GET.get('userid')

            if not action or not userid:
                return JsonResponse({"code": 400, "error": "缺少必要参数"})

            if action == 'collect':
                goodsid = request.GET.get('goodsid')
                count = request.GET.get('count', 1)
                action_type = request.GET.get('action_type', '浏览')
                input_str = f"{userid},{goodsid},{count},{action_type}"
                res = agent.invoke(input_str)

            elif action == 'similarity':
                res = agent.invoke(userid)
                num = 10
                input_str = f"{userid},{num}"
                res = agent.invoke(input_str)

            else:
                return JsonResponse({"code": 400, "error": "未知操作类型"})

            return JsonResponse({"code": 200, "data": res})

        except Exception as e:
            return JsonResponse({"code": 500, "error": str(e)})